package com.ruiyian.netty.nettyserver;

import com.ruiyian.common.constant.Constants;
import com.ruiyian.common.constant.HttpStatus;
import com.ruiyian.common.core.domain.AjaxResult;
import com.ruiyian.common.core.domain.entity.Point;
import com.ruiyian.common.core.redis.RedisCache;
import com.ruiyian.common.enums.ReturnType;
import com.ruiyian.common.utils.DateUtils;
import com.ruiyian.common.utils.JSON;
import com.ruiyian.common.utils.jfinsUtil.FinsMessage;
import com.ruiyian.common.utils.jfinsUtil.FinsMessageUtils;
import com.ruiyian.common.utils.spring.SpringUtils;
import com.ruiyian.netty.nettyclient.NettyStarter;
import com.ruiyian.project.equipment.service.PointService;
import com.ruiyian.project.log.domain.PlanLogMain;
import com.ruiyian.project.log.service.PlanLogMainService;
import com.ruiyian.system.domain.ServerEq;
import com.ruiyian.system.service.ServerService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author chong
 * @email 673426164@qq.com
 * @create 2020/12/21
 * Desc: 建立连接
 */
@Slf4j
public class MyWebSocketHandler extends ChannelInboundHandlerAdapter {
    // 预案信息
//    private static PlanService planService;
    private static RedisCache redisCache;
    private static PointService pointService;
    private static Redisson redisson;
    private String type;
    private static ServerService serverService;
    private static PlanLogMainService planLogMainService;
    private List<Point> data;

    static {
        planLogMainService = SpringUtils.getBean(PlanLogMainService.class);
//        planService = SpringUtils.getBean(PlanService.class);
        redisCache = SpringUtils.getBean(RedisCache.class);
        pointService = SpringUtils.getBean(PointService.class);
        redisson = SpringUtils.getBean(Redisson.class);
        serverService = SpringUtils.getBean(ServerService.class);
    }

    public MyWebSocketHandler(String type) {
        this.type = type;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.info(type + "-->与websocket客户端" + host + ":" + port + "建立连接!");
        //添加到channelGroup通道组
        if (Constants.TCP.equals(type)) {
            MyChannelHandlerPool.tcpChannelGroup.add(ctx.channel());
        } else {
            MyChannelHandlerPool.channelGroup.add(ctx.channel());
        }
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("与客户端断开连接，通道关闭！");
        //移除channelGroup 通道组
        if (Constants.TCP.equals(type)) {
            MyChannelHandlerPool.tcpChannelGroup.remove(ctx.channel());
        } else {
            MyChannelHandlerPool.channelGroup.remove(ctx.channel());
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        JSONObject json = null;
        try {
            PlanLogMain planLogMain = new PlanLogMain(); // 联动预案日志
            InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
            int port = ipSocket.getPort();
            String host = ipSocket.getHostString();
            log.info(type + "-->接收到是host：" + host + "端口:" + port);
            String operateType = "";
            String lockKey = "client_port_model";
            // 如果是tcp的话
            if (Constants.TCP.equals(type)) {
                ByteBuf in = (ByteBuf) msg;
                int readableBytes = in.readableBytes();
                byte[] bytes = new byte[readableBytes];
                in.readBytes(bytes);
                String str = new String(bytes);
                log.info("服务端接收到消息:" + str);
                json = JSONObject.fromObject(str);
                planLogMain.setConditions(str);
            }
            // 如果是websocket的话
            else {
                TextWebSocketFrame frame = (TextWebSocketFrame) msg;
                log.info("服务端接收到消息：" + frame.text());
                json = JSONObject.fromObject(frame.text());
                planLogMain.setConditions(frame.text());
            }
            // *******判断是获取哪一种结构物数据
            // 如果是操作/获取桥梁数据（udp设备）
            if(json.get("moniStructure").equals("bridge")){
                // 如果是确认连接消息
                if(json.get("type").equals("connect")){
                    Map<String, String> returnDataMap = new HashMap<>();
                    returnDataMap.put("type", "connect");
                    Map<String, String> bridgeDataMap = redisCache.getCacheObject((String) json.get("equipmentId"));
                    // 数据时间与当前机器时间差不超过30秒，就断定设备没掉线
                    if(Math.abs(Long.parseLong(bridgeDataMap.get("timestamp"))-System.currentTimeMillis())/1000<30){
                        returnDataMap.put("connectstatus", "true");
                    }
                    else {
                        returnDataMap.put("connectstatus", "false");
                    }
                    // 将连接成功的状态发给websocket前端
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.stringify(returnDataMap)));
                }
                else if(json.get("type").equals("query")){
                    // 前端需要发送设备id过来，获取对应的数据
                    Map<String, String> returnDataMap = redisCache.getCacheObject((String) json.get("equipmentId"));
                    returnDataMap.put("type", "query");
                    // 将数据通过websocket发回给前端
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.stringify(returnDataMap)));
                }
                else if(json.getString("type").equals("close")){
                    // 客户端关闭连接,websocket移除连接（这个步骤是必要的）
                    MyChannelHandlerPool.channelGroup.remove(ctx.channel());
                    return;
                }
            }
            // 如果是操作/获取隧道数据（tcp设备，包括fins设备）
            else if(json.get("moniStructure").equals("tunnel")) {
                // 如果网页端发来关闭连接，那就关闭连接
                if (json.getString("type").equals("close")) {
                    Map<String, ChannelFuture> connectMap = NettyStarter.getConnectMap();
                    for(Map.Entry<String, ChannelFuture> entry : connectMap.entrySet()){
                        ChannelFuture channelFuture = entry.getValue();
                        // 及时关闭tcp连接，确保plc不出问题
                        channelFuture.channel().close();
                    }
                    // 客户端关闭连接,websocket移除连接
                    MyChannelHandlerPool.channelGroup.remove(ctx.channel());
                    return;
                }
                // 区分是哪个模块
                // 视觉诱导模块
                if(json.get("moduleId").equals(162)){
                    // 创建发送给plc的fins消息
                    FinsMessage finsMessage = null;
                    // 筛选出服务列表
                    List<ServerEq> visualinductionserverList = serverService.selectServerByTunnelId(json.getLong("tunnelId"), json.getLong("moduleId"));
                    Map<String, ChannelFuture> connectMap = NettyStarter.getConnectMap(); // 通过netty获取所有连接状态
                    // 对服务列表进行筛选，只保留处于连接的服务，
                    for(int i = 0; i < visualinductionserverList.size(); i++) {
                        if(!connectMap.containsKey(visualinductionserverList.get(i).getHost() + ":" +visualinductionserverList.get(i).getPort())){
                            visualinductionserverList.remove(i);
                            i--;
                        }
                    }
                    if(visualinductionserverList.size()==0){
                        json.put("type", json.get("type"));
                        json.put(AjaxResult.CODE_TAG, HttpStatus.BAD_REQUEST);
                        json.put(AjaxResult.MSG_TAG, "plc连接失败");
                        json.put("tunnelId", json.getLong("tunnelId"));
                        json.put("moduleId", json.containsKey("moduleId") ? json.getLong("moduleId") : null);
                        // 发送plc连接失败的信息给网页
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(json.toString()));
                        // 下面这个不知道干嘛的
//                        MyChannelHandlerPool.tcpChannelGroup.writeAndFlush(Unpooled.wrappedBuffer(JSON.stringify(json).getBytes(CharsetUtil.UTF_8)));
                        log.info("tunnelId:"+json.getLong("tunnelId")+" moduleId:"+json.getLong("moduleId")+" 设备连接失败");
                        return;
                    }
                    // 写操作
                    if(json.get("type").equals("write")){
                        String serverpath = visualinductionserverList.get(0).getHost()+":"+visualinductionserverList.get(0).getPort();
                        finsMessage = FinsMessageUtils.createWriteMessage(json.getInt("memoryArea"), json.getInt("registerAddress"),
                            json.getInt("bits"), json.getInt("value"), serverpath);
                        // 二次判断，看fins数据是否生成
                        if (finsMessage != null){
                            ChannelFuture channelFuture = connectMap.get(serverpath);
                            // 把fins消息发送给plc
                            if (channelFuture != null && channelFuture.isSuccess()) {
                                // 将finse数据发给plc
                                channelFuture.channel().writeAndFlush(finsMessage.toString()).sync();
                                // 将操作成功的数据上发给网页端
                                ctx.channel().writeAndFlush(new TextWebSocketFrame(json.toString()));
                            }
                        }
                    }
                }
                // 隧道消防模块
                if(json.get("moduleId").equals(17)){
                    // 筛选出服务列表
                    List<ServerEq> fireboxserverList = serverService.selectServerByTunnelId(json.getLong("tunnelId"), json.getLong("moduleId"));
                }
                // 二次事故管控模块（plc-fins设备）
                else if(json.get("moduleId").equals(18)){
                    //
                    // 创建发送给plc的fins消息
                    FinsMessage finsMessage = null;
                    // 统计连接PLC服务状态
                    // 先根据隧道id、以及模块id获取plc列表，以便筛选出本模块对应的设备
                    List<ServerEq> serverList = serverService.selectServerByTunnelId(json.getLong("tunnelId"), json.getLong("moduleId"));
                    Map<String, ChannelFuture> connectMap = NettyStarter.getConnectMap(); // 通过netty获取所有连接状态
                    // 对服务列表进行筛选，只保留处于连接的服务，
                    for(int i = 0; i < serverList.size(); i++) {
                        if(!connectMap.containsKey(serverList.get(i).getHost() + ":" +serverList.get(i).getPort())){
                            serverList.remove(i);
                            i--;
                        }
                    }
                    // 如果服务列表为0，则说明没有连接，发送连接失败信息，然后直接返回
                    if(serverList.size()==0){
                        json.put("type", json.get("type"));
                        json.put(AjaxResult.CODE_TAG, HttpStatus.BAD_REQUEST);
                        json.put(AjaxResult.MSG_TAG, "plc连接失败");
                        json.put("tunnelId", json.getLong("tunnelId"));
                        json.put("moduleId", json.containsKey("moduleId") ? json.getLong("moduleId") : null);
                        // 发送plc连接失败的信息给网页
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(json.toString()));
                        // 下面这个不知道干嘛的
//                        MyChannelHandlerPool.tcpChannelGroup.writeAndFlush(Unpooled.wrappedBuffer(JSON.stringify(json).getBytes(CharsetUtil.UTF_8)));
                        log.info("tunnelId:"+json.getLong("tunnelId")+" moduleId:"+json.getLong("moduleId")+" 设备连接失败");
                        return;
                    }
                    // 完整plc服务设备的地址
                    String serverpath = serverList.get(0).getHost()+":"+serverList.get(0).getPort();
                    // *********判断是否为查询状态的指令
                    if (json.getString("type").equals(ReturnType.QUERY.getCode())) {
                        operateType = ReturnType.QUERY.getCode();
                        // 点位数据直接从前端取
                        finsMessage = FinsMessageUtils.createReadFineMessage(json.getInt("memoryArea"), json.getInt("registerAddress"),
                                                                             json.getInt("length"), serverpath, Constants.DEFAULT_SA1);
                    }
                    // ***********判断是否为写设备指令
                    else if (json.getString("type").equals(ReturnType.WRITE.getCode())) {
                        operateType = ReturnType.WRITE.getCode();
                        // 点位数据直接从前端取
                        finsMessage = FinsMessageUtils.createWriteMessage(json.getInt("memoryArea"), json.getInt("registerAddress"),
                                                                          json.getInt("bits"), json.getInt("value"), serverpath);
                    }
                    else {
                        json.put("type", json.get("type"));
                        json.put(AjaxResult.CODE_TAG, HttpStatus.BAD_REQUEST);
                        json.put(AjaxResult.MSG_TAG, "操作指令错误");
                        json.put("tunnelId", json.getLong("tunnelId"));
                        json.put("moduleId", json.containsKey("moduleId") ? json.getLong("moduleId") : null);
                        // 发送操作指令的信息给网页
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(json.toString()));
                        log.info("操作指令错误");
                        return;
                    }
                    // 二次判断，看fins数据是否生成
                    if (finsMessage != null) {
                        RLock redissonLock = redisson.getLock(lockKey);
                        // 锁  缓存redis
                        redissonLock.lock(3, TimeUnit.SECONDS);
                        log.info(DateUtils.dateTimeNow());
                        redisCache.setCacheObject(Constants.NOWINDEX, operateType.equals(ReturnType.QUERY.getCode()) ? 0 : 1); // 读取数据下标位
                        // 返回数据
                        redisCache.deleteObject(Constants.RETURNPOINT);
                        // 操作点位集合
                        redisCache.deleteObject(Constants.POINTLIST);
//                        redisCache.setCacheList(Constants.POINTLIST, pointList);
                        redisCache.setCacheObject("type", json.containsKey("type") ? json.getString("type") : 0);
//                        redisCache.setCacheObject("groupNum", json.containsKey("groupNum") ? json.getInt("groupNum") : 0);
                        redisCache.setCacheObject("tunnelId", json.getLong("tunnelId"));
                        redisCache.setCacheObject("moduleId", json.containsKey("moduleId") ? json.getLong("moduleId") : null);
                        redisCache.setCacheObject("memoryArea", json.containsKey("memoryArea") ? json.getLong("memoryArea") : null);
                        redisCache.setCacheObject("registerAddress", json.containsKey("registerAddress") ? json.getLong("registerAddress") : null);
                        redisCache.setCacheObject("recoderserverpath", serverpath);
                        ChannelFuture channelFuture = connectMap.get(serverpath);
                        // 创建上传的数据
                        json.put("operateType", operateType);
                        json.put(AjaxResult.CODE_TAG, HttpStatus.SUCCESS);
                        json.put(AjaxResult.MSG_TAG, "plc操作成功");
                        json.put("tunnelId", json.getLong("tunnelId"));
                        json.put("moduleId", json.containsKey("moduleId") ? json.getLong("moduleId") : null);
                        // 把fins消息发送给plc
                        if (channelFuture != null && channelFuture.isSuccess()) {
                            // 将finse数据发给plc
                            channelFuture.channel().writeAndFlush(finsMessage.toString()).sync();
                            // 将操作成功的数据上发给网页端
                            ctx.channel().writeAndFlush(new TextWebSocketFrame(json.toString()));
                        }
                    }
                }
            }
//            planLogMain.setParamValue(JSON.stringify(pointList));
//            planLogMain.setCreateTime(new Date());
           /* ThreadPoolUtil.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        planLogMainService.save(planLogMain);
                    } catch (Exception e) {
                        log.error("执行日志保存失败"+e.getMessage());
                    }
                }
            });*/
        } catch (Exception e) {
            log.error(e.getMessage());
            json.put(AjaxResult.CODE_TAG, HttpStatus.BAD_REQUEST);
            json.put(AjaxResult.MSG_TAG, "操作异常");
            json.put("moduleId", json.containsKey("moduleId") ? json.getLong("moduleId") : null);
            ctx.channel().writeAndFlush(new TextWebSocketFrame(json.toString()));
            MyChannelHandlerPool.tcpChannelGroup.writeAndFlush(Unpooled.wrappedBuffer(JSON.stringify(json).getBytes(CharsetUtil.UTF_8)));
        } finally {
            ReferenceCountUtil.release(msg);
        }

    }
}
